// Copyright 2021 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package meta

import (
	"strconv"

	"github.com/pingcap/errors"
	"github.com/pingcap/tidb/pkg/meta/model"
)

var _ AutoIDAccessor = &autoIDAccessor{}

// AutoIDAccessor represents the entry to retrieve/mutate auto IDs.
type AutoIDAccessor interface {
	Get() (int64, error)
	Put(val int64) error
	Inc(step int64) (int64, error)
	CopyTo(databaseID, tableID int64) error
	Del() error
}

type autoIDAccessor struct {
	m          *Mutator
	databaseID int64
	tableID    int64

	idEncodeFn func(int64) []byte
}

// Get implements the interface AutoIDAccessor.
func (a *autoIDAccessor) Get() (int64, error) {
	m := a.m
	return m.txn.HGetInt64(m.dbKey(a.databaseID), a.idEncodeFn(a.tableID))
}

// Put implements the interface AutoIDAccessor.
func (a *autoIDAccessor) Put(val int64) error {
	m := a.m
	return m.txn.HSet(m.dbKey(a.databaseID), a.idEncodeFn(a.tableID), []byte(strconv.FormatInt(val, 10)))
}

// Inc implements the interface AutoIDAccessor.
func (a *autoIDAccessor) Inc(step int64) (int64, error) {
	m := a.m
	// Note that the databaseID may not match the current table,
	// it may come from the original schema id the table was created
	// in, but to allow concurrent use across renames etc. we keep
	// the full ID (Schema ID + Table ID) as is.
	// Meaning we cannot verify only the schema id.
	// And a rename may have happened before the first id is set,
	// as well as dropping the original schema.
	// So no Schema ID or Table ID verifications can be done.
	dbKey := m.dbKey(a.databaseID)
	tblKey := a.idEncodeFn(a.tableID)
	return m.txn.HInc(dbKey, tblKey, step)
}

// Del implements the interface AutoIDAccessor.
func (a *autoIDAccessor) Del() error {
	m := a.m
	dbKey := m.dbKey(a.databaseID)
	if err := m.txn.HDel(dbKey, a.idEncodeFn(a.tableID)); err != nil {
		return errors.Trace(err)
	}
	return nil
}

var _ AutoIDAccessors = &autoIDAccessors{}

// CopyTo implements the interface AutoIDAccessor.
// It's used to copy the current meta to another table after rename table
func (a *autoIDAccessor) CopyTo(databaseID, tableID int64) error {
	curr, err := a.Get()
	if err != nil {
		return err
	}
	// There is no need to copy zero value. And in certain case, this can overwrite
	// the existing auto ID of the target table, for example, when restoring data with
	// a higher version of BR (≥ 8.5) into a lower version of TiDB (≤ 7.5):
	// In older versions of TiDB, the job worker handles auto ID during rename
	// table by copying the old value, deleting it, and then setting the new value.
	// However, in newer versions of TiDB, auto ID is rebased when applying the info
	// schema diff. This means that when BR applies the new diff here, the original
	// value have already been deleted. In such cases, the existing value will be
	// overwritten with zero, which is incorrect.
	if curr == 0 {
		return nil
	}
	m := a.m
	return m.txn.HSet(m.dbKey(databaseID), a.idEncodeFn(tableID), []byte(strconv.FormatInt(curr, 10)))
}

// AutoIDAccessors represents all the auto IDs of a table.
type AutoIDAccessors interface {
	Get() (model.AutoIDGroup, error)
	Put(autoIDs model.AutoIDGroup) error
	Del() error

	AccessorPicker
}

// AccessorPicker is used to pick a type of auto ID accessor.
type AccessorPicker interface {
	RowID() AutoIDAccessor
	RandomID() AutoIDAccessor
	IncrementID(tableVersion uint16) AutoIDAccessor

	SequenceValue() AutoIDAccessor
	SequenceCycle() AutoIDAccessor
}

type autoIDAccessors struct {
	access autoIDAccessor
}

const sepAutoIncVer = model.TableInfoVersion5

// Get implements the interface AutoIDAccessors.
func (a *autoIDAccessors) Get() (autoIDs model.AutoIDGroup, err error) {
	if autoIDs.RowID, err = a.RowID().Get(); err != nil {
		return autoIDs, err
	}
	if autoIDs.IncrementID, err = a.IncrementID(sepAutoIncVer).Get(); err != nil {
		return autoIDs, err
	}
	if autoIDs.RandomID, err = a.RandomID().Get(); err != nil {
		return autoIDs, err
	}
	return
}

// Put implements the interface AutoIDAccessors.
func (a *autoIDAccessors) Put(autoIDs model.AutoIDGroup) error {
	if err := a.RowID().Put(autoIDs.RowID); err != nil {
		return err
	}
	if err := a.IncrementID(sepAutoIncVer).Put(autoIDs.IncrementID); err != nil {
		return err
	}
	return a.RandomID().Put(autoIDs.RandomID)
}

// Del implements the interface AutoIDAccessors.
func (a *autoIDAccessors) Del() error {
	if err := a.RowID().Del(); err != nil {
		return err
	}
	if err := a.IncrementID(sepAutoIncVer).Del(); err != nil {
		return err
	}
	return a.RandomID().Del()
}

// RowID is used to get the _tidb_rowid meta key-value accessor.
func (a *autoIDAccessors) RowID() AutoIDAccessor {
	a.access.idEncodeFn = a.access.m.autoTableIDKey
	return &a.access
}

// IncrementID is used to get the auto_increment ID meta key-value accessor.
func (a *autoIDAccessors) IncrementID(tableVersion uint16) AutoIDAccessor {
	// _tidb_rowid and auto_increment ID in old version TiDB share the same meta key-value.
	if tableVersion < sepAutoIncVer {
		a.access.idEncodeFn = a.access.m.autoTableIDKey
	} else {
		a.access.idEncodeFn = a.access.m.autoIncrementIDKey
	}
	return &a.access
}

// RandomID is used to get the auto_random ID meta key-value accessor.
func (a *autoIDAccessors) RandomID() AutoIDAccessor {
	a.access.idEncodeFn = a.access.m.autoRandomTableIDKey
	return &a.access
}

// SequenceValue is used to get the sequence value key-value accessor.
func (a *autoIDAccessors) SequenceValue() AutoIDAccessor {
	a.access.idEncodeFn = a.access.m.sequenceKey
	return &a.access
}

// SequenceCircle is used to get the sequence circle key-value accessor.
func (a *autoIDAccessors) SequenceCycle() AutoIDAccessor {
	a.access.idEncodeFn = a.access.m.sequenceCycleKey
	return &a.access
}

// NewAutoIDAccessors creates a new AutoIDAccessors.
func NewAutoIDAccessors(m *Mutator, databaseID, tableID int64) AutoIDAccessors {
	return &autoIDAccessors{
		autoIDAccessor{
			m:          m,
			databaseID: databaseID,
			tableID:    tableID,
		},
	}
}
